package com.syezon.android.base.download;

import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class DownloadTask implements Runnable {
    private static final String TAG = DownloadTask.class.getSimpleName();
    private static final String TEMP_FILE_SUFFIX = ".temp";
    private DownloadInfo downloadInfo;
    private DownloadInfoDao downloadInfoDao;
    private String id;
    private long totalSize;
    private long completedSize;         //  Download section has been completed
    //    private float percent;        //  Percent Complete
    private double downloadSpeed;
    private String url;
    private String saveDirPath;
    private RandomAccessFile file;
    private int UPDATE_SIZE = 50 * 1024;    // The database is updated once every 50k
    private int downloadStatus = DownloadStatus.DOWNLOAD_STATUS_INIT;
    private String fileName;    // File name when saving
    private String fileNameTemp; // Temp File Name
    private List<IDownloadTaskListener> listeners = new ArrayList<>();

    public DownloadTask() {
    }

    public static DownloadTask parse(DownloadInfo entity) {
        DownloadTask task = new DownloadTask();
        task.setDownloadStatus(entity.getDownloadStatus());
        task.setId(entity.getDownloadId());
        task.setUrl(entity.getUrl());
        task.setFileName(entity.getFileName());
        task.setSaveDirPath(entity.getSaveDirPath());
        task.setCompletedSize(entity.getCompletedSize());
        task.setDownloadInfo(entity);
        task.setTotalSize(entity.getTotalSize());
        return task;
    }

    @Override
    public void run() {
        if (downloadStatus == DownloadStatus.DOWNLOAD_STATUS_PAUSE) {
            return;
        }
        downloadStatus = DownloadStatus.DOWNLOAD_STATUS_PREPARE;
        onPrepare();
        HttpURLConnection httpURLConnection = null;
        InputStream inputStream = null;
        BufferedInputStream bis = null;
        try {
            File saveFolder = new File(saveDirPath);
            if (!saveFolder.exists()) {
                saveFolder.mkdirs();
            }
            File finalFile = new File(saveDirPath + fileName);
            if (finalFile.exists() && finalFile.length() > 0) {
                long finalFileLength = finalFile.length();
                totalSize = completedSize = finalFileLength;
                downloadStatus = DownloadStatus.DOWNLOAD_STATUS_COMPLETED;
                DownloadInfo completeDownloadInfo = new DownloadInfo(id, finalFileLength, finalFileLength, url, saveDirPath, fileName, DownloadStatus.DOWNLOAD_STATUS_COMPLETED);
                downloadInfoDao.update(completeDownloadInfo);
                onCompleted();
                return;
            }
            downloadInfo = downloadInfoDao.find(id);
            fileNameTemp = new StringBuilder(fileName).append(TEMP_FILE_SUFFIX).toString();
            file = new RandomAccessFile(saveDirPath + fileNameTemp, "rwd");
            if (downloadInfo != null) {
                completedSize = downloadInfo.getCompletedSize();
                totalSize = downloadInfo.getTotalSize();
            }
            if (file.length() < completedSize) {
                completedSize = file.length();
            }
            long fileLength = file.length();
            if (fileLength != 0 && downloadInfo != null && totalSize != 0 && totalSize <= fileLength) {
                downloadStatus = DownloadStatus.DOWNLOAD_STATUS_COMPLETED;
                totalSize = completedSize = fileLength;
                downloadInfo.setDownloadStatus(downloadStatus);
                downloadInfo.setCompletedSize(completedSize);
                downloadInfoDao.update(downloadInfo);
                onCompleted();
                return;
            }
            downloadStatus = DownloadStatus.DOWNLOAD_STATUS_START;
            if (downloadInfo == null) {
                downloadInfo = new DownloadInfo(id, totalSize, completedSize, url, saveDirPath, fileName, downloadStatus);
            }
            downloadInfo.setCompletedSize(completedSize);
            downloadInfo.setDownloadStatus(downloadStatus);
            downloadInfoDao.update(downloadInfo);
            onStart();
            httpURLConnection = (HttpURLConnection) new URL(url).openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setReadTimeout(50000);
            httpURLConnection.setConnectTimeout(50000);
            httpURLConnection.setRequestProperty("RANGE", "bytes=" + completedSize + "-");
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == 200 || responseCode == 206) {
                int contentLength = httpURLConnection.getContentLength();
                if (contentLength > 0) {
                    if (totalSize <= 0) {
                        totalSize = contentLength;
                    }
                    downloadStatus = DownloadStatus.DOWNLOAD_STATUS_DOWNLOADING;
                    downloadSpeed = 0.00;
                    downloadInfo.setTotalSize(totalSize);
                    downloadInfo.setDownloadStatus(downloadStatus);
                    downloadInfoDao.update(downloadInfo);
                    if (TextUtils.isEmpty(httpURLConnection.getHeaderField("Content-Range"))) {
                        //返回的没有Content-Range 不支持断点下载 需要重新下载
                        File alreadyDownloadedFile = new File(saveDirPath + fileNameTemp);
                        if (alreadyDownloadedFile.exists()) {
                            alreadyDownloadedFile.delete();
                        }
                        file = new RandomAccessFile(saveDirPath + fileNameTemp, "rwd");
                        completedSize = 0;
                    }
                    file.seek(completedSize);
                    inputStream = httpURLConnection.getInputStream();
                    bis = new BufferedInputStream(inputStream);
                    byte[] buffer = new byte[2 * 1024];
                    int length = 0;
                    int buffOffset = 0;
                    long timeStart = System.currentTimeMillis();
                    downloadInfo.setCompletedSize(completedSize);
                    downloadInfo.setDownloadStatus(downloadStatus);
                    downloadInfoDao.update(downloadInfo);
                    while ((length = bis.read(buffer)) > 0 && downloadStatus != DownloadStatus.DOWNLOAD_STATUS_CANCEL && downloadStatus != DownloadStatus.DOWNLOAD_STATUS_PAUSE) {
                        file.write(buffer, 0, length);
                        completedSize += length;
                        buffOffset += length;
                        if (buffOffset >= UPDATE_SIZE) {
                            long downloadLength = buffOffset;
                            // Update download information database
                            buffOffset = 0;
                            //这两句根据需要自行选择是否注释，注释掉的话由于少了数据库的读取，速度会快一点，但同时如果在下载过程程序崩溃的话，程序不会保存最新的下载进度,并且下载过程不会更新进度
                            downloadInfo.setCompletedSize(completedSize);
                            downloadStatus = DownloadStatus.DOWNLOAD_STATUS_DOWNLOADING;
                            downloadInfo.setDownloadStatus(downloadStatus);
                            downloadInfoDao.update(downloadInfo);
                            long duration = System.currentTimeMillis() - timeStart;
                            timeStart = System.currentTimeMillis();
                            double speed = downloadLength * 1000 / 1024 / duration;
                            if (speed > 0.0) {
                                downloadSpeed = speed;
                            }
                            Log.d(TAG, downloadInfo.toString());
                            onDownloading();
                        }
                    }
                    downloadSpeed = 0.00;
                    downloadInfo.setCompletedSize(completedSize);
                    downloadInfo.setDownloadStatus(downloadStatus);
                    downloadInfoDao.update(downloadInfo);
                    onDownloading();
                }
            } else {
                downloadSpeed = 0.00;
                downloadStatus = DownloadStatus.DOWNLOAD_STATUS_ERROR;
                downloadInfo.setCompletedSize(completedSize);
                downloadInfo.setDownloadStatus(downloadStatus);
                downloadInfoDao.update(downloadInfo);
                onError(IDownloadTaskListener.DOWNLOAD_ERROR_IO_ERROR);
                return;
            }

        } catch (FileNotFoundException e) {
            downloadSpeed = 0.00;
            downloadStatus = DownloadStatus.DOWNLOAD_STATUS_ERROR;
            downloadInfo.setCompletedSize(completedSize);
            downloadInfo.setDownloadStatus(downloadStatus);
            downloadInfoDao.update(downloadInfo);
            onError(IDownloadTaskListener.DOWNLOAD_ERROR_FILE_NOT_FOUND);
            return;
        } catch (IOException e) {
            downloadSpeed = 0.00;
            downloadStatus = DownloadStatus.DOWNLOAD_STATUS_ERROR;
            downloadInfo.setCompletedSize(completedSize);
            downloadInfo.setDownloadStatus(downloadStatus);
            downloadInfoDao.update(downloadInfo);
            onError(IDownloadTaskListener.DOWNLOAD_ERROR_IO_ERROR);
            return;
        } finally {
            if (bis != null) try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (inputStream != null) try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (file != null) try {
                file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        downloadSpeed = 0.00;
        if (totalSize > 0 && totalSize == completedSize) {
            downloadStatus = DownloadStatus.DOWNLOAD_STATUS_COMPLETED;
        }
        downloadInfo.setDownloadStatus(downloadStatus);
        downloadInfoDao.update(downloadInfo);
        Log.d(TAG, downloadInfo.toString());
        switch (downloadStatus) {
            case DownloadStatus.DOWNLOAD_STATUS_COMPLETED:
                File tempCompleteFile = new File(saveDirPath + fileNameTemp);
                File finalCompleteFile = new File(saveDirPath + fileName);
                if (!finalCompleteFile.exists()) {
                    tempCompleteFile.renameTo(finalCompleteFile);
                }
                onCompleted();
                break;
            case DownloadStatus.DOWNLOAD_STATUS_PAUSE:
                onPause();
                break;
            case DownloadStatus.DOWNLOAD_STATUS_CANCEL:
                downloadInfoDao.remove(downloadInfo);
                File temp = new File(saveDirPath + fileNameTemp);
                if (temp.exists()) temp.delete();
                onCancel();
                break;
        }
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public float getPercent() {
        return totalSize == 0 ? 0 : completedSize * 100 / totalSize;
    }

    public long getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(long toolSize) {
        this.totalSize = toolSize;
    }

    public long getCompletedSize() {
        return completedSize;
    }

    public void setCompletedSize(long completedSize) {
        this.completedSize = completedSize;
    }

    public String getSaveDirPath() {
        return saveDirPath;
    }

    public void setSaveDirPath(String saveDirPath) {
        this.saveDirPath = saveDirPath;
    }

    public int getDownloadStatus() {
        return downloadStatus;
    }

    public void setDownloadStatus(int downloadStatus) {
        this.downloadStatus = downloadStatus;
    }

    public void setDownloadDao(DownloadInfoDao downloadInfoDao) {
        this.downloadInfoDao = downloadInfoDao;
    }

    public void setDownloadInfo(DownloadInfo downloadInfo) {
        this.downloadInfo = downloadInfo;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public double getDownloadSpeed() {
        return downloadSpeed;
    }

    public void setDownloadSpeed(double downloadSpeed) {
        this.downloadSpeed = downloadSpeed;
    }

    public void cancel() {
        setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_CANCEL);
        File temp = new File(saveDirPath + fileName);
        if (temp.exists()) temp.delete();
    }

    public void pause() {
        setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_PAUSE);
    }

    private void onPrepare() {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onPrepare(this);
            }
        }
    }

    private void onStart() {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onStart(this);
            }
        }
    }

    private void onDownloading() {
        Log.d("onDownloading", id + "listener size:" + listeners.size());
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onDownloading(this);
            }
        }
    }

    private void onCompleted() {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onCompleted(this);
            }
        }
    }

    private void onPause() {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onPause(this);
            }
        }
    }

    private void onCancel() {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onCancel(this);
            }
        }
    }

    private void onError(int errorCode) {
        for (IDownloadTaskListener listener : listeners) {
            if (listener != null) {
                listener.onError(this, errorCode);
            }
        }
    }

    public void addDownloadListener(IDownloadTaskListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    /**
     * if listener is null,clear all listener
     *
     * @param listener
     */
    public void removeDownloadListener(IDownloadTaskListener listener) {
        if (listener != null) {
            listeners.remove(listener);
        }
    }

    public void removeAllDownloadListener() {
        listeners.clear();
    }

    @Override
    public String toString() {
        return "DownloadDBEntity{" +
                "downloadId=" + id +
                ", completedSize='" + completedSize + '\'' +
                ", totalSize=" + totalSize +
                ", url='" + url + '\'' +
                ", saveDirPath='" + saveDirPath + '\'' +
                ", fileName='" + fileName + '\'' +
                ", downloadStatus=" + downloadStatus +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        DownloadTask that = (DownloadTask) o;
        if (id != null ? !id.equals(that.id) : that.id != null) return false;
        return url != null ? url.equals(that.url) : that.url == null;
    }
}
